package com.googlecode.cswish.struts.spring;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.hibernate.Query;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.transaction.annotation.Transactional;

import com.googlecode.cswish.Config;
import com.googlecode.cswish.annotation.GenericType;
import com.googlecode.cswish.annotation.ParamScope;
import com.googlecode.cswish.annotation.Protected;
import com.googlecode.cswish.exception.AppException;
import com.googlecode.cswish.model.ModelInfo;
import com.googlecode.cswish.model.PageElements;
import com.googlecode.cswish.model.PageInfo;
import com.googlecode.cswish.model.PropertyInfo;
import com.googlecode.cswish.model.Reference;
import com.googlecode.cswish.oosearch.Condition;
import com.googlecode.cswish.oosearch.QLInfo;
import com.googlecode.cswish.oosearch.QLParse;
import com.googlecode.cswish.util.BlankObject;
import com.googlecode.cswish.util.OgnlExUtil;

/**
 * CRUD operation and OO search
 * 
 * @author Jerry date 2005-7-4 22:33:46
 * @see GenericJPAService
 */
//@Service
@Transactional
public class GenericHibernateService extends HibernateDaoSupport implements GenericService {
	@Resource
	protected QLParse qlParse;
	
	@Resource
	protected ObjectFactory objectFactory;
	
	@Resource
	protected BeanIntrospector beanIntrospector;
	
	@Resource
	protected OgnlExUtil ognlUtil;
	
	@Resource
	protected Config config;

	/**
     * Search one page of data
     * 
     * @param model
     * @param pageNo
     * @param newSearch
     * @return
     */
	@ParamScope
    public <T> PageInfo<T> search(
    		@GenericType T model, String keywords,
    		int start, int limit, String sort, String dir, boolean newSearch,
    		Reference<QLInfo> qlInfoRef, @ParamScope(visible=false, value="#ps")PageElements ps) {
		// get it from pageInfo
		QLInfo qlInfo = qlInfoRef.get();
		if (newSearch || qlInfo == null) {
			if (model == null) {
				// try to initialize the model according the request action name
				model = (T) objectFactory.buildBean(GenericType.MODEL, Object.class, null);
			}
			qlInfo = qlParse.getSelectHQL(model, Condition.DEFAULT);
			qlInfoRef.set(qlInfo);
		}
		
		if (start == 0 && limit == 0) {
			limit = config.getPageSize();
		} else if (limit - start > config.getPageSize() * 10) {
			logger.warn("Search too much, there're performance problems! limit the total size to ten times of config.pageSize");
			limit = start + config.getPageSize() * 10;
		}
		qlInfo.setFirstResult(start);
		qlInfo.setMaxResults(limit);
		qlInfo.setSort(sort);
		qlInfo.setDir(dir);
		
		PageInfo pageInfo = null;
		int count = getCount(qlInfo, false);
    	if (count > 0 && qlInfo.getFirstResult() < count) {
    		pageInfo = searchByQl(qlInfo, false);
    		pageInfo.setTotalCount(count);
    	} else {
    		pageInfo = new PageInfo(config.getPageSize());
    		pageInfo.setData(new ArrayList(0));
    	}
		return pageInfo;
	}
    
    /**
     * Convert model to QL expression and return the table result
     * 
     * @param model
     * @param condition
     * @return
     */
    @Protected
    public <T> PageInfo searchByModel(T model, Condition condition) {
    	PageInfo<T> pageInfo = null;
    	if (model != null) {
	    	QLInfo qlInfo = qlParse.getFromClause(model, condition);
	    	int count = getCount(qlInfo, false);
	    	
	    	if (count > 0 && condition.getOffset() < count) {
	    		pageInfo = searchByQl(qlParse.toSelectHQL(qlInfo), false);
	    		pageInfo.setTotalCount(count);
	    	}
    	} else {
    		logger.debug("parameter 'model' is null");
    	}
    	
    	if (pageInfo == null) {
    		pageInfo = new PageInfo<T>(config.getPageSize());
    		pageInfo.setData(new ArrayList<T>(0));
    	}
    	return pageInfo;
    }
    
    /**
     * search all the data result
     * 
     * @param model
     * @return
     */
    public <T> PageInfo<T> searchAll(@GenericType T model) {
    	QLInfo qlInfo = qlParse.getFromClause(model, Condition.DEFAULT);
    	// TODO: use lazy collection to improve the performance
    	PageInfo<T> pageInfo = searchByQl(qlParse.toSelectHQL(qlInfo), false);
    	return pageInfo;
    }
    
    /**
     * calculate the record count
     * 
     * @param qlInfo
     * @return
     */
    @Protected
    public int getCount(QLInfo qlInfo, boolean cacheable) {
    	String ql = qlInfo.getQl();
		int index = ql.lastIndexOf(" order by ");
		if (index > 0) {
			ql = ql.substring(0, index);
		}
		
		// get the total count
		String countHql = "select count(a) " + ql;		
		if (logger.isDebugEnabled()) {
			String oldHql = qlInfo.getQl();
			qlInfo.setQl(countHql);
			logger.debug(qlInfo);
			qlInfo.setQl(oldHql);
		}
		// TODO: process cache
		List value = getHibernateTemplate().find(countHql, qlInfo.getParams().toArray());
		int count = Integer.parseInt(value.get(0).toString());
		return count;
    }
    
    /**
     * search table data
     * 
     * @param qlInfo
     * @return
     */
    @Protected
    public PageInfo searchByQl(QLInfo qlInfo, boolean cacheable) {
    	PageInfo pageInfo = new PageInfo(config.getPageSize());
    	if (qlInfo.getMaxResults() != 0) {
			// pager query
    		String ql;
        	if (qlInfo.getSort() != null) {
        		ql = qlInfo.getQl() + " order by " + qlInfo.getSort() + ' ' + qlInfo.getDir();
        	} else {
        		ql = qlInfo.getQl();
        	}
    		Query query = getSession().createQuery(ql);
    		if (cacheable) {
    			query.setCacheable(true);
    		}
    		if (qlInfo.getParams() != null) {
        		int index = 1;
        		for (Object value : qlInfo.getParams()) {
    				query.setParameter(index++, value);
    			}
        	}
			query.setFirstResult(qlInfo.getFirstResult());
			query.setMaxResults(qlInfo.getMaxResults());

			List<?> result = query.list();
			int size = qlInfo.getMaxResults();
			pageInfo.setPageSize(size);
			pageInfo.setPageNoByRowNo(qlInfo.getFirstResult());
			pageInfo.setData(result);
		} else {
			// query all data
			List<?> result = getHibernateTemplate().find(qlInfo.getQl(), qlInfo.getParams().toArray());
			int size = result == null? 0: result.size();
			pageInfo.setPageSize(size);
			pageInfo.setTotalCount(size);
			pageInfo.setData(result);
		}
    	return pageInfo;
    }
    
    public <T> List<T> addList(List<T> model) {
    	List<T> result = new ArrayList<T>(model.size());
    	for (T theModel : model) {
    		result.add(add(theModel));
    	}
    	return result;
    }
    
    public <T> T add(@GenericType T model) {
    	if (load(model) != null) {
    		throw new AppException("errors.duplicate", getId(model));
    	}
    	
    	if (model != null) {
    		model = (T) getHibernateTemplate().save(model);
    		
    		try {
    			getHibernateTemplate().flush();
    		} catch (RuntimeException ex) {
    			cleanId(new IdentityHashMap<Object, Object>(), model);
    			throw ex;
    		}
    	}
    	return model;
    }
    
    private void cleanId(Map<Object, Object> checkModels, Object model) {
    	if (checkModels.containsKey(model)) {
    		return;
    	}
    	
    	checkModels.put(model, model);
    	ModelInfo modelInfo = beanIntrospector.getModelInfo(model.getClass().getName());
		for (PropertyInfo propertyInfo : modelInfo.getPersistenceProperties()) {
			if (propertyInfo.isAutoId()) {
				setId(model, null);
			} else if (!propertyInfo.isSimpleObj()) {
				Object subModel = null;
				try {
					Object[] params = null;
					subModel = propertyInfo.getReadMethod().invoke(model, params);
				} catch (Exception ex) {
					logger.error(ex);
				}
				
				if (subModel != null) {
					if (subModel instanceof Collection) {
						Collection<Object> coll = (Collection<Object>)subModel;
						for (Object m : coll) {
							cleanId(checkModels, m);
						}
					} else {
						cleanId(checkModels, subModel);
					}
				}
			}
		}
    }
    
    private Object getId(Object model) {
		Object id = ognlUtil.getValue("id", BlankObject.Map, model);
		return id;
    }
    
    private void setId(Object model, Object id) {
		id = ognlUtil.getValue("id", BlankObject.Map, model);
		ognlUtil.setValue("id", BlankObject.Map, model, id);
    }
    
    public void updateList(List<Object> model) {
    	for (Object theModel : model) {
    		update(theModel);
    	}
    }
    
    public void update(@GenericType Object model) {
    	Object id = getId(model);
    	if (id == null) {
    		throw new AppException("error.missId");
    	}

        // update
    	getHibernateTemplate().update(model);
    }

    public <T> T merge(T model) {
    	return (T) getHibernateTemplate().merge(model);
    }

    public boolean deleteList(List<Object> model) {
    	boolean ret = true;
    	for (Object theModel : model) {
    		ret &= delete(theModel);
    	}
    	return ret;
    }
    
    public boolean delete(@GenericType Object model) {
    	// TODO: check the model id?
    	if (model != null) {
    		model = load(model);
    		if (model != null) {
    			getHibernateTemplate().delete(model);
    			return true;
    		}
    	}
    	return false;
    }
    
    public Object _update(@GenericType Object model) {
    	ognlUtil.getOgnlUtil().copy(load(model), model, BlankObject.Map);
    	return model;
    }
    
    public Object view(@GenericType Object model) {
    	return load(model);
    }
    
    @Protected
    public <T> T load(T model) {
    	Object id = getId(model);
    	if (id != null && id instanceof Serializable) {
    		getHibernateTemplate().load(model, (Serializable)id);
    		return model;
    	} else {
    		return null;
    	}
    }

	@Override
	public <T> QLInfo buildQlInfo(T model, String keywords, int start, int limit, String sort, String dir,
			boolean newSearch, Reference<QLInfo> qlInfoRef, PageElements ps) {
		// TODO Auto-generated method stub
		return null;
	}
}